import uvicorn
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from typing import Optional
from datetime import datetime
from models.lottery import LotteryManager, LotteryDraw
from models.config import LOTTERY_TYPES
from utils.common import setup_logger
import logging
import os
import pandas as pd
import csv
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger

# 导入所有爬虫
from crawlers.official_crawlers import SSQCrawler, DLTCrawler, FC3DCrawler, PL3Crawler, PL5Crawler, QXCCrawler, KL8Crawler, QLCCrawler
from crawlers.backup_crawler import (
    SSQBackupCrawler, FC3DBackupCrawler, KL8BackupCrawler, QLCBackupCrawler,
    PL3BackupCrawler, DLTBackupCrawler, PL5BackupCrawler, QXCBackupCrawler
)

# 设置日志
logger = setup_logger('main', 'logs/main.log')
logger.info("启动彩票数据服务系统")

# 爬虫映射表
CRAWLER_MAP = {
    'ssq': SSQCrawler,
    'dlt': DLTCrawler,
    '3d': FC3DCrawler,
    'pl3': PL3Crawler,
    'pl5': PL5Crawler,
    '7xc': QXCCrawler,
    'kl8': KL8Crawler,
    '7lc': QLCCrawler
}
BACKUP_CRAWLER_MAP = {
    'ssq': SSQBackupCrawler,
    'dlt': DLTBackupCrawler,
    '3d': FC3DBackupCrawler,
    'pl3': PL3BackupCrawler,
    'pl5': PL5BackupCrawler,
    '7xc': QXCBackupCrawler,
    'kl8': KL8BackupCrawler,
    '7lc': QLCBackupCrawler
}

# 创建FastAPI应用
app = FastAPI(title="彩票数据服务")
logger.info("初始化FastAPI应用")

# 配置CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
logger.info("配置CORS中间件")

# 创建调度器
scheduler = BackgroundScheduler()

def read_lottery_data(lottery_type: str, limit: int = 30) -> list:
    """读取本地彩票数据"""
    try:
        config = LOTTERY_TYPES[lottery_type]
        file_path = config['data_file']
        if not os.path.exists(file_path):
            return []
        
        df = pd.read_csv(file_path)
        return df.tail(limit).to_dict('records')
    except Exception as e:
        logger.error(f"读取本地数据失败: {str(e)}")
        return []

def get_current_datetime() -> datetime:
    """获取当前日期时间"""
    return datetime.now()

def is_lottery_day(lottery_type: str) -> bool:
    """判断今天是否是开奖日"""
    today = get_current_datetime().weekday()
    config = LOTTERY_TYPES[lottery_type]
    return today in config['draw_days']

def is_after_draw_time(lottery_type: str) -> bool:
    """判断是否超过开奖时间"""
    current_time = get_current_datetime().time()
    config = LOTTERY_TYPES[lottery_type]
    return current_time >= config['draw_time']

def check_crawler_limit() -> bool:
    """检查爬虫调用次数限制"""
    try:
        file_path = "data/crawler_calls.csv"
        if not os.path.exists(file_path):
            return True
        
        df = pd.read_csv(file_path)
        today = get_current_datetime().strftime("%Y-%m-%d")
        today_calls = len(df[df['date'] == today])
        return today_calls < 10  # 每天最多调用10次
    except Exception as e:
        logger.error(f"检查爬虫调用次数失败: {str(e)}")
        return False

def record_crawler_call():
    """记录爬虫调用"""
    try:
        file_path = "data/crawler_calls.csv"
        today = get_current_datetime().strftime("%Y-%m-%d")
        
        with open(file_path, 'a', newline='') as f:
            writer = csv.writer(f)
            if not os.path.exists(file_path):
                writer.writerow(['date', 'time'])
            writer.writerow([today, get_current_datetime().strftime("%H:%M:%S")])
    except Exception as e:
        logger.error(f"记录爬虫调用失败: {str(e)}")

@app.on_event("startup")
async def startup_event():
    """启动事件"""
    # 确保数据目录存在
    os.makedirs("data", exist_ok=True)
    
    # 启动调度器
    scheduler.start()
    
    # 为每种彩票添加定时任务
    for lottery_type, config in LOTTERY_TYPES.items():
        crawler_class = CRAWLER_MAP[lottery_type]
        draw_days = ','.join([str(day) for day in config['draw_days']])
        draw_time = config['draw_time']
        
        scheduler.add_job(
            crawler_class().fetch_latest_draw,
            CronTrigger(
                day_of_week=draw_days,
                hour=draw_time.hour,
                minute=draw_time.minute + 1
            ),
            id=f'fetch_{lottery_type}_data'
        )

@app.get("/")
async def root():
    """根路径"""
    return {
        "code": 1,
        "message": "success",
        "data": "彩票数据服务运行中"
    }

@app.get("/api/lottery/types")
async def get_lottery_types():
    """获取支持的彩票类型"""
    return {
        "code": 1,
        "message": "success",
        "data": [
            {
                "type": key,
                "name": value["name"],
                "draw_days": value["draw_days"],
                "draw_time": value["draw_time"].strftime("%H:%M")
            }
            for key, value in LOTTERY_TYPES.items()
        ]
    }

def _get_weekday_name(day: int) -> str:
    """获取星期名称"""
    weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
    return weekdays[day]

def is_valid_number(num):
    """检查是否为有效数字"""
    if isinstance(num, str):
        return num.strip() != ''
    return pd.notna(num)

@app.get("/api/lottery/latest")
async def get_latest_lottery(lottery_type: str):
    """获取最新一期开奖数据，主爬虫和备用爬虫都抓取，优先用当天数据，不一致时以当天为准"""
    logger.info(f"获取最新开奖数据，彩票类型: {lottery_type}")
    try:
        if lottery_type not in LOTTERY_TYPES:
            return {"code": 0, "message": f"不支持的彩票类型: {lottery_type}", "data": None}

        # 检查是否是开奖日
        if not is_lottery_day(lottery_type):
            return {"code": 0, "message": f"今天不是{LOTTERY_TYPES[lottery_type]['name']}开奖日", "data": None}

        # 检查是否过了开奖时间
        if not is_after_draw_time(lottery_type):
            return {"code": 0, "message": f"未到{LOTTERY_TYPES[lottery_type]['name']}开奖时间", "data": None}

        crawler_class = CRAWLER_MAP.get(lottery_type)
        backup_crawler_class = BACKUP_CRAWLER_MAP.get(lottery_type)
        if not crawler_class or not backup_crawler_class:
            return {"code": 0, "message": f"未找到{LOTTERY_TYPES[lottery_type]['name']}爬虫", "data": None}

        crawler = crawler_class()
        backup_crawler = backup_crawler_class()

        main_draw = crawler.fetch_latest_draw()
        logger.info(f"主爬虫获取到数据: {main_draw}")
        backup_draw = backup_crawler.fetch_latest_draw()
        logger.info(f"备用爬虫获取到数据: {backup_draw}")
        if not main_draw and not backup_draw:
            return {"code": 0, "message": f"主爬虫和备用爬虫均未获取到{LOTTERY_TYPES[lottery_type]['name']}开奖数据", "data": None}

        today = datetime.now().strftime("%Y-%m-%d")
        logger.info(f"当前日期: {today}")
        use_draw = None
        def backup_to_draw(backup_draw):
            red = backup_draw.get('red_numbers')
            blue = backup_draw.get('blue_number')
            return LotteryDraw(
                draw_number=backup_draw['draw_number'],
                draw_date=backup_draw['draw_date'],
                red_numbers=red,
                blue_number=blue
            )

        if main_draw and backup_draw:
            main_red = getattr(main_draw, 'red_numbers', None)
            main_blue = getattr(main_draw, 'blue_number', None)
            backup_red = backup_draw.get('red_numbers')
            backup_blue = backup_draw.get('blue_number')
            if (
                main_draw.draw_number == backup_draw['draw_number'] and
                main_red == backup_red and
                str(main_blue) == str(backup_blue)
            ):
                use_draw = main_draw
                logger.info(f"主爬虫和备用爬虫数据一致，使用主爬虫数据: {use_draw}")
            else:
                if main_draw.draw_date == today:
                    use_draw = main_draw
                elif backup_draw.get('draw_date') == today:
                    use_draw = backup_to_draw(backup_draw)
                    logger.info(f"主爬虫和备用爬虫数据不一致，使用备用爬虫数据: {use_draw}")
                else:
                    logger.error(f"主爬虫和备用爬虫数据不一致，且都不是当天数据，主爬虫数据: {getattr(main_draw, '__dict__', main_draw)}, 备份爬虫数据: {backup_draw}")
                    return {"code": 0, "message": f"主爬虫和备用爬虫数据不一致，且都不是当天数据", "data": None}
        elif main_draw:
            use_draw = main_draw
            logger.info(f"只有主爬虫数据，使用主爬虫数据: {use_draw}")
        else:
            use_draw = backup_to_draw(backup_draw)
            logger.info(f"只有备用爬虫数据，使用备用爬虫数据: {use_draw}")

        # 新增：判断最终数据是否为当天
        logger.info(f"最终数据: {use_draw}")
        if not use_draw or str(use_draw.draw_date) != today:
            return {
                "code": 0,
                "message": f"未获取到{LOTTERY_TYPES[lottery_type]['name']}当天开奖数据",
                "data": None
            }

        # 保存数据
        crawler.save_draws([use_draw])

        # 保持原有格式化返回数据部分不变
        weekday_names = ['一', '二', '三', '四', '五', '六', '日']
        weekday = weekday_names[datetime.now().weekday()]
        date_str = datetime.now().strftime("%Y-%m-%d")
        
        if lottery_type in ['ssq', 'dlt', '7lc', '7xc']:
            # 双色球、大乐透、七乐彩格式
            return {
                "code": 1,
                "message": "success",
                "data": f"当日{LOTTERY_TYPES[lottery_type]['name']}开奖号码（{date_str}（{weekday}））：\n"
                        f"期号：{use_draw.draw_number}\n"
                        f"红球：{use_draw.red_numbers}\n"
                        f"蓝球：{use_draw.blue_number}\n"
                        f"以官方公布为准"
            }
        elif lottery_type == 'kl8':
            # 快乐8格式
            return {
                "code": 1,
                "message": "success",
                "data": f"当日{LOTTERY_TYPES[lottery_type]['name']}开奖号码（{date_str}（{weekday}））：\n"
                        f"期号：{use_draw.draw_number}\n"
                        f"红球：{use_draw.red_numbers}\n"
                        f"以官方公布为准"
            }
        else:
            # 其他彩票格式（3D、排列3、排列5、七星彩）
            return {
                "code": 1,
                "message": "success",
                "data": f"当日{LOTTERY_TYPES[lottery_type]['name']}开奖号码（{date_str}（{weekday}））：\n"
                        f"期号：{use_draw.draw_number}\n"
                        f"号码：{use_draw.red_numbers}\n"
                        f"以官方公布为准"
            }
    except Exception as e:
        logger.error(f"获取{lottery_type}开奖数据时发生错误: {str(e)}")
        return {"code": 0, "message": f"获取{LOTTERY_TYPES[lottery_type]['name']}开奖数据时发生错误: {str(e)}", "data": None}

if __name__ == "__main__":
    # 确保日志目录存在
    os.makedirs("logs", exist_ok=True)
    
    logger.info("启动FastAPI服务")
    uvicorn.run(app, host="0.0.0.0", port=8000) 